\documentclass[11pt] {article}
\usepackage[pdftex]{graphicx}
\usepackage{amssymb,amsmath}
\usepackage[dutch]{babel}


\newenvironment{changemargin}[2]{%
 \begin{list}{}{%
  \setlength{\topsep}{0pt}%
  \setlength{\leftmargin}{#1}%
  \setlength{\rightmargin}{#2}%
  \setlength{\topmargin}{-2.5cm}%
  \setlength{\textheight}{1.3\textheight}%
  \setlength{\listparindent}{\parindent}%
  \setlength{\itemindent}{\parindent}%
  \setlength{\parsep}{\parskip}%
 }%
\item[]}{\end{list}}

\title {
  Gedistribueerde Systemen\\
  De Happy Chatters (Groep 4)
}

\author {
  Rik van der Kooij (6064965) \\
  Fabien Tesselaar (6131085) \\
  Martijn van der Veen (5964008) \\
  Gijs van der Voort (6191053) \\
}

\date {\today}

\begin {document}


%% TODO:
% - evt. test-code schrijven
% - evt. bot schrijven
% - verslag af + doorlezen


%%% DEADLINE: Zondag 5 juni 2011 om 23:59:59 uur.


%%% INLEVEREN: (bakkerr@science.uva.nl met als subject "[DS] Inleveren opgave T04", in tar of tgz formaat)

%%    De laatste versie van de code van de server
%%    Een README file met daarin de instructies voor compileren en draaien op de edu0xx machines.
%%    De code die je hebt gebruikt om je server te testen (unit-tests, stress-tests, etc.)
%%    Het verslag in pdf.


%%% VERSLAG: (bevat in ieder geval maar niet uitsluitend)


%%    Titelpagina met namen van de groepsleden en teamnummer.
\maketitle
\thispagestyle {empty} % geen paginanummer
\newpage


%%    Inhoudsopgave
\tableofcontents
\newpage

%%    Inleiding met probleembeschrijving
\section{Inleiding}
In den beginne van de computernetwerken ontstond de client-serverarchitectuur. Het bleek voor veel toepassingen handig om een computer aan te wijzen als centraal punt, waarna andere computers hiermee konden verbinden voor bijvoorbeeld het uitwisselen van bestanden, of later het bekijken van een webpagina of het uitwisselen van e-mailberichten.

Toen het aantal computers dat gebruik maakte van zo'n centrale server toe begon te nemen, werden de eisen voor servers groot. Belangrijker nog is de afhankelijkheid voor de clients van de centrale server; computers hebben vrijwel nooit 100\% uptime en een uitgevallen server zorgt voor een grote groep verdrietige clients.

Om netwerken betrouwbaarder te maken kunnen meerdere servers samenwerken in een zogenaamd \emph{gedistribueerd systeem}. De workload (vaak clients) wordt zo gelijkmatig mogelijk verdeeld over de verschillende servers, waarmee wordt voorkomen dat servers overbelast raken. Bovendien kunnen ververs uitvallen zonder dat het systeem omvalt; andere servers nemen de taken van uitgevallen servers dan over.

Het doel van dit practicum is het schrijven van een gedistribueerd chatsysteem. Meerdere clients moeten kunnen verbinden met de backbone van servers. De servers communiceren onderling om te zorgen voor een goede afhandeling van aan- en afmelding van clients en het versturen van berichten. Het systeem moet bestand zijn tegen uitvallende servers, waarna de overige servers moeten herstructureren en de overgebleven clients moeten blijven bedienen. Voor de co\"ordinatie van de IP-adressen van de servers onderling wordt gebruikgemaakt van een centrale control server.

De client en control server worden aangeboden, de server wordt door verschillende groepen apart ge\"implementeerd aan de hand van het meegeleverde protocol. Vervolgens zullen de servers worden opgestart in een gemixte omgeving, en getest of de verschillende servers kunnen samenwerken met de clients, de control server en met elkaar.

In de volgende sectie wordt kort de van tevoren gemaakte beslissingen en planning besproken, gevolgd door een korte review van het te implementeren protocol. Vervolgens wordt de implementatie toegelicht en worden kort enkele tijdens de implementie ondervonden problemen besproken. In de sectie daarna volgt een uitgebreide evaluatie voor de testdag, de uiteindelijke code en de samenwerking. Tot slot worden nog enkele opmerkingen gemaakt als feedback naar de practicumopdracht.


%%    Kort stukje over de planning (samenvatting PvA, welke taal, waarom, wie, wanneer)
\section{Planning}

De server zal in zeven weken worden gebouwd. Een goede planning is dus belangrijk. Korte toelichting voor de gekozen programmeertaal, en de oorspronkelijke planning, zijn hierna te vinden.

\subsection{Programmeertaal}
% Implementatiekeuze (welke taal, platform, waarom, is het stabiel, is het robuust, wat zijn voordelen, nadelen, heb je meerdere opties overwogen?)
Voor de implementatie is gekozen voor de programmeertaal Python (versie 2.5+). Python is platformonafhankelijk en draait daarom op Linux, Mac OS X en Windows. De UvA-computers ondersteunen python 2.5 en worden gebruikt op de testdag, dus ook met deze wat oude versie moet rekening gehouden worden.

Er is niet gekozen voor C of C++, omdat programmeren in deze talen vaak voor extra programmeerwerk zorgt door geheugenmanagement en de enigszins primitieve mogelijkheden (zeker in C dat niet Objectgeori\"enteerd is). In Java en Python zitten meer mogelijkheden in de taal ingebouwd. Daarnaast hebben beide talen behoorlijk uitgebreide libraries, onder andere voor sockets en threads. Beide programmeertalen draaien op een virtuele machine waardoor extra controles kunnen worden uitgevoerd en het geheel stabieler, maar iets langzamer, is. Bovendien is goede error-handling aanwezig in de taal en zijn eventueel ontstane run-time errors voorzien van een duidelijke traceback. Python heeft bovendien een interactieve shell, waarin snel code kan worden getest.

Vanwege de vaak compacte Pythonprogramma's en de uitdaging (voor drie teamleden) van het leren van eene nieuwe programmeertaal, is besloten het systeem in Python te programmeren.


\pagebreak

\subsection{Taakverdeling}

\subsubsection{Wie}
\begin{itemize}
  \item Leer Python: Rik, Fabi\"en, Gijs
  \item Code:
  \begin{itemize}
    \item Framework: Martijn
    \item Thread met loop met luisterende socket: Rik
    \item Zenden van (alle typen) packages: Fabi\"en
    \item Protocol:
    \begin{itemize}
      \item Opstarten en afsluiten server: Gijs
      \item Logs bijhouden: Gijs
      \item Client-server communication: Martijn
      \item Pakketten van andere servers: Rik
    \end{itemize}
  \end{itemize}
  \item Communicatie met practicumassistent, deadlines \& inleveren: Gijs
  \item Verslagen: Allen (eigen deel implementatie)
\end{itemize}

\subsubsection{Wat \& Wanneer}
\begin{tabular}{l | l}
  Wat             & Wanneer \\ \hline
  Opzetten project, repository en leren Python    & Week 13 \\
  Plan van Aanpak                                 & Week 13, 14 \\
  Opzetten framework                              & Week 14 \\
  Socketverbinding, begin protocol                & Week 15, 16 \\
  Begin verslag                                   & Week 16 \\
  Protocol af, multithreaded maken                & Week 17 \\
  Software afmaken                                & Week 18 \\
  Testen server, debuggen                         & Week 18, 19 \\
  Verbeteringen doorvoeren                        & Week 19 \\
  Afmaken verslag                                 & Week 20, 21 \\
  Gezamenlijk testen, vuurwerk en bier            & Week 20
\end{tabular}

\pagebreak

\section{Protocol}

Een kort overzicht van het gedistribueerde netwerk en een korte analyse het protocol volgen, als inleiding op de implementatie.

\subsection{Netwerktopologie}
Elke server krijgt door de control server een \emph{parent} toegewezen. Meerdere servers kunnen dezelfde parent hebben. Op deze manier ontstaat er een hierarchie van servers zonder loops, waardoor niet hoeft te worden bijgehouden welke netwerkpakketjes al voorbij zijn gekomen. De clients verbinden met een van de servers die ze kennen of door de control server krijgen toegewezen. Het zo ontstane netwerk is weergegeven in Figuur 1.

\begin{figure}
  \centering
  \includegraphics[width=1.2\textwidth]{img/netwerk-topologie}
  \caption{Netwerktopologie}
\end{figure}

\subsection{Protocolanalyse}
Bij het opstarten van de server en het afmelden van een server op het netwerk zal de server (zonder input) enkele packages moeten versturen naar de control server, de naburige server(s) en eventueel verbonden clients. Daarnaast worden er ping-requests gestuurd naar verbonden servers en clients als er 30 seconden geen gehoor is gegeven. In alle andere gevallen stuurt de server een antwoord op een binnengekomen package.

Zo is de reactie op een Ping (140) een Pong (150). Ontvangen berichten van een verbonden client (200/210) of van een andere server (300/310) worden bij bestemming \#all naar alle verbonden clients en servers gestuurd, en bij een specifieke bestemming ofwel naar de directe client (indien aan deze server verbonden), ofwel naar de dichtstbijzijnde server in het pad naar de client. Bij een 700 (stop server) wordt de server actief afgesloten.

Een uitgewerkt diagram voor het verbinden van een nieuwe client (ontvangen 100), het wijzigen van de naam van een client (ontvangen 160) en het verbinden van een server (bij opstarten, regroup of bij ontvangen 600) zijn te vinden in respectievelijk Figuur 2, Figuur 3 en Figuur 4. Om de diagrammen simpel te houden gaan we voor de diagrammen van een foutloze (error-vrije) communicatie uit. Ping en enkele andere berichten verwachten een antwoord binnen 2 seconden. Dit is niet expliciet opgenomen in de diagrammen.

\begin{figure}
  \centering
  \includegraphics[width=0.8\textwidth]{img/Protocol_connect_client}
  \caption{Client maakt verbinding met onze server}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.8\textwidth]{img/Protocol_change_client_name}
  \caption{Client verandert naam}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.8\textwidth]{img/Protocol_connect_server}
  \caption{Server maakt verbinding}
\end{figure}




\pagebreak
%%    De implementatie, wat heb je gedaan, wat kwam je tegen, wat was lastig, wat was de oplossing?
\section{Implementatie}

Na het maken van een globaal framework, konden de benodigde basisfuncties en het protocol worden ge\"implementeerd. Nu volgt een korte uitleg, die als introductie gezien moet worden voor de broncode. Voor meer details verwijzen we naar de broncode, die te vinden is in Appendix A. Ook worden enkele problemen besproken die we tegenkwamen tijdens het proces.


\subsection{Toelichting programmacode}

\subsubsection{Objecten}
Er is voor een objectge\"orienteerde aanpak gekozen. Drie objecten zijn gedefinieerd:

\begin{enumerate}
 \item \textbf{Server} \\
       De belangrijkste module is de server zelf. Deze zorgt voor de initialisatie, het lezen van het configuratiebestand, het maken van een unieke tag, het opzetten van de socket, het opstarten van de benodigde threads en het starten van de loop die op packages wacht. Binnen Server worden tevens lijsten van verbonden clients en servers bijgehouden.
 \item \textbf{Protocol} \\
       Het protocol is ge\"implementeerd in een aparte class. Voor een binnenkomend package wordt een methode \emph{handle\_input} aangeroepen, welke het package verder afhandeld. De protocol-class neemt aan de hand van het package-nummer gepaste acties, waaronder eventueel het verzenden van andere packages. Ook zijn er functies gedefinieerd voor het opstarten en afsluiten van de server.

       Voor elk package-type dat kan worden ontvangen door de server is een aparte functie gedefinieerd (\emph{handle\_NR}). Voor elk package-type dat kan worden verzonden door de server is ook een functie gedefinieerd (elk met een beschrijvende naam + NR). In de functies voor ontvangen packages worden desgewenst variabelen in Server aangepast, en functies aangeroepen voor het verzenden van packages.

 \item \textbf{Package} \\
       De packages zijn gedefinieerd als object en de class Package bezit enkele functies voor het in- en uitpakken van de package-code en de werkelijke content (nul, een of meer woorden, opgeslagen in een lijst).
\end{enumerate}


\subsubsection{Threads}
% threads
Voor het betrouwbaar verwerken van alle packages die binnenkomen is gekozen voor een aanpak waarbij verschillende threads worden opgestart.

\begin{enumerate}
 \item \textbf{Server} \\
       De server zelf is de main-thread. In deze thread draait de loop die luistert op de socket naar nieuwe packages. Ook is dit de thread die de andere threads start.
 \item \textbf{Ping-thread} \\
       Wordt er 30 seconden niets van een verbonden client of server vernomen, dan moet er een ping worden verzonden om te kijken of de client of server als disconnected moet worden bestempeld. Deze thread houdt de tijd goed in de gaten, en stuurt pings indien nodig. Is er twee seconden later nog steeds niets vernomen (ook geen reactie op een ping), dan wordt de desbetreffende client of server gedisconnect.
 \item \textbf{Responses-thread} \\
       % TODO: deze thread is stiekem helemaal niet geimplementeerd
       Bepaalde verzonden packages vragen om een reactie. Zodra een packet wordt verwacht, wordt het aan de lijst self.responses toegevoegd. Deze thread bekijkt de lijst regelmatig om te controleren of bepaalde verwachte packages te lang op zich laten wachten. Op dit moment zijn er geen packages geimplementeerd die door deze thread worden opgewacht. Deze extra check zou echter voor een extra stabiele server kunnen zorgen, indien gewenst. 
 \item \textbf{HandleInput-thread} \\
       Zodra een package is ontvangen moet deze worden behandeld. De Server-thread moet echter blijven luisteren op de socket voor de aankomst van nieuwe packages. Daarom wordt bij het ontvangen van een package een thread opgestart die dit package behandeld, gebruikmakend van de Protocol-class.
\end{enumerate}

% mutexes
De threads maken gebruik van dezelfde variabelen in de class Server. Daarom worden mutexes gebruikt voor het verkrijgen en vrijgeven van locks. De variabelen voor de parent server, de children (overige servers) en de verbonden clients hebben een mutex. Bij het schrijven van de code is gelet op het eventueel kunnen voorkomen van deadlocks.


\subsubsection{Usage}
Het opstarten van een losse server gebeurt met het aanroepen van main.py. Het al dan niet opstarten in debug-mode, als wel het instellen van variabelen als het poortnummer van de luisterende socket en de locatie van de control server, zijn in te stellen in het bestand settings.xml.
\begin{verbatim}
 $ cd src/server/
 $ python main.py
\end{verbatim}

Voor het opstarten van een control server, \'e\'en of meerdere servers en \'e\'en of meerdere clients is een tweede script geschreven, test.py. 

\begin{verbatim}
 $ cd src/
 $ python test.py
 Amount of servers you wish to simulate?
 1
 and how many clients?
 1
 Press any key to exit.
\end{verbatim}


\subsection{Moeilijkheden}
Voor drie van de vier groepsgenoten was een van de doelen in dit practicum het leren van een nieuwe programmeertaal. Ook het werken met een versiebeheer (Mercurial) en Latex zat nog niet in de routine. Na een voorspoedige analyse van de opdracht (waar volgens ons toen en nu een aantal onduidelijkheden in te vinden zijn), leek de voortgang dus een week of twee niet zo hard te gaan. In de tussentijd echter leerden het team nuttige vaardigheden welke niet alleen in dit, maar ook in latere projecten van pas kunnen komen.

Het leren van een nieuwe (programmeer)taal vraagt enige gewenning. Zo was het gebruik van sockets en het maken van packages daarvoor iets om de handleiding eens goed op na te slaan. Ook het gebruik van Threads liet in het begin wachten op enkele vallende kwartjes. Gelukkig hebben modules in Python duidelijke documentatie. Het duurde daarnaast even voordat het framework zo ver was ingevuld dat we konden beginnen met testen. Een groot deel van het systeem moest ge\"implementeerd worden voordat er enig zichtbaar werkend resultaat was. Dit werd deels veroorzaakt doordat getest werd met de al voltooide control server en client, waardoor sommige delen van het protocol alleen in hun geheel te testen waren.

% hier kan ook wat mooiers / vrolijkers van gemaakt worden
De specificatie van het protocol was daarnaast niet genoeg voor een straightforward implementatie; op sommige plekken moesten een aantal aannames worden gemaakt. Het implementeren van package-type 110 heeft behoorlijk wat tijd in beslag genomen, met name door de uitgebreidheid van zaken die gedaan moeten worden bij het ontvangen van dit type package. Ook de 604 gaf enige problemen. Met behulp van het eerder gemaakte Plan Van Aanpak en wat denkwerk op papier is het uiteindelijk gelukt beide te implementeren, waarna de andere package-typen relatief eenvoudig in te vullen waren.

Vanwege de relatieve simpliciteit van het protocol waren enkele zaken niet helemaal duidelijk. Wat moet er bijvoorbeeld gebeuren als een lijst namen binnenkomt, maar er raakt een package verloren? Er is geen package voor het laten herhalen van een bepaald package. In dit geval kan men dus alleen de desbetreffende server kicken en hopen dat bij opnieuw aanmelden geen packages verloren gaan. Ook zijn er packages die bij het verloren gaan helemaal niet worden opgemerkt, zoals een 130 (client verwijderd). Hiervoor zou weer een soort ontvangstbevestiging voor moeten worden bedacht. Er zouden voor dit soort gevallen timers kunnen worden ingebouwd, waarna de al gedefinieerde response-thread kan gaan waarschuwen als geen ontvangstbevestigingen zijn ontvangen. Ook wordt vaak een reeks namen gestuurd, terwijl er maar \'e\'en naam nieuw is. Zo zijn er meer ineffici\"enties.

In de gevallen waarbij niet precies duidelijk is wat er wordt verwacht of er simpelweg packages missen in het protocol die het probleem netjes zouden oplossen, hebben we vaak de meest simpele (maar niet elegante) oplossing gekozen of verondersteld dat het probleem niet voor zou komen. Dit zou kunnen worden opgelost door een uitgebreider protocol te maken met meer check-packages.


\pagebreak
%%    Evaluatie van de testdag (wat ging er mis en waarom, is dat nu opgelost?)
\section{Evaluatie}

\subsection{Testdag}
Tijdens de gezamenlijke testdag werden een aantal test uitgevoerd. Hier volgt een kort overzicht van de tests, met het resultaat.

\begin{enumerate}
 \item \textbf{Individuele computer} \\
       Een eigen control server, \'e\'en server en enkele clients worden opgestart. De clients kregen elkaars namen te zien en konden met elkaar praten. Na het disconnecten van een client zagen de andere clients de client verdwijnen. Test geslaagd.
 \item \textbf{Eigen serverpark} \\
       Een eigen control server, twee servers en enkele clients worden opgestart op \'e\'en computer. Dezelfde resultaten zorgen wederom voor een geslaagde test.

       Vervolgens werd de control server, een server en wat clients op de ene pc opgestart, en een andere server en wat clients op een andere pc. Hierbij bleken in eerste instantie de clients alleen met de clients op dezelfde pc te kunnen communiceren. Dit kwam omdat de servers niet met elkaar verbonden waren. Na het bestuderen van de logboeken bleek waarom: de control server stuurde naar de server op de andere pc dat de server op '127.0.0.1' oftewel localhost te bereiken was. Dat was namelijk het IP-adres dat de control server van het ontvangen package van de server extraheerde. De control server starten op een derde pc loste het probleem op. Hierna konden de clients praten met alle clients op alle pc's in het opgezette netwerk. Na het handmatig killen van een van de servers rapporteerden de clients na 30 seconden inderdaad het verdwijnen van de clients die verbonden waren met de be\"eindigde server. Wederom een geslaagde test.
 \item \textbf{Gezamenlijke test} \\
       Elke groep start enkele servers op en laat een aantal clients verbinden met een door de control server voorgestelde server. Wij starten er allevier een op, met elk twee clients. Het geheel werkt, en chatten met de medestudenten gaat goed. Af en toe starten we een extra server op. Na enige tijd blijken nieuwe opgestarte servers verschillende lijsten clients te ontvangen. Dit blijkt bij logboekinspectie niet aan onze server te liggen: onze servers krijgen simpelweg verschillende lijsten van hun (verschillende) parent-servers. Kennelijk is de hierarchie gesplitst door een fout van een server van een andere groep.
 \item \textbf{Stresstest} \\
       Botjes worden opgestart om het systeem om zeep te helpen. Er gebeurt niet veel, en de clients en servers blijven lang verbonden, totdat een bot begint met eindeloos veel clients op te starten. Bij het bereiken van enkele honderden clients stoppen enkele clients met het weergeven van verzonden berichten. Uiteindelijk sluiten ook een aantal van onze servers. Waar dit aan ligt is niet helemaal duidelijk. Doordat hele lijsten met clients naar alle servers en na het aanmelden van elke nieuwe client worden verzonden, raakt het geheel overbelast en vermoedelijk kunnen de FNWI-computers het niet aan om al die packages te verwerken in de veelheid aan opgestarte (concurrent) threads. Het is lastig te wapenen tegen dit soort distributed denial of service of DDoS-aanvallen.

       Een andere aanval die volgens ons zou moeten werken is de volgende. Stuur een aardige hoeveelheid aanmeld-requests (600) gevolgd door quits (701) naar de control server. Verzamel hierbij de ontvangen IP-adressen en poortnummers van voorgestelde parent-servers in het netwerk. Met deze lijst kan bijvoorbeeld naar alle (bekende) servers een aantal Registreer Server (600) berichten worden gestuurd uit naam van andere servers (met UDP is IP-spoofing makkelijk). Het gevolg is vermoedelijk allerlei loops in de 'hierarchie' en een veelheid aan dataverkeer bij elk verstuurd bericht. Een mogelijke oplossing is het sturen van een check-bericht naar de desbetreffende server met een of ander unieke tag, die de server terug moet sturen als hij echt een nieuwe child-server wil zijn. Ook kunnen allerlei berichten (bijvoorbeeld hergroepeer) worden verzonden uit naam van de control server. Dit is wellicht op te lossen door gebruik te maken van public key systemen. Deze idee\"en hebben we niet ge\"implementeerd.
\end{enumerate}


\pagebreak
%%    Evaluatie van je uiteindelijke versie (wat is de maximale belasting van je systeem en hoe heb je dat getest?, Hoe werkt de server samen met die van anderen, waarom werkt dat (niet)?)
% TODO: dit is een sectie waar we puntenaftrek voor gaan krijgen. Maar geen tijd en/of zin om uitgebreid te gaan testen en meten.
\subsection{Overige tests}
Tijdens het schrijfproces zijn enkele unit-tests uitgevoerd. Met behulp van de Package-class zijn delen van het geimplementeerde protocol (600, 110) in communicatie met de control server en de client getest. Testen was echter lastig omdat, zoals eerder opgemerkt, een groot deel van het protocol gebruikt wordt voor vele sequenties van packages. Het emuleren van de control server en client was daarbij extra werk. De meeste tests zijn uitgevoerd door de eigen server en de geleverde control server en client op te starten, waarna verschillende basisfuncties (aanmelden client, versturen bericht, verdwijnen server) simpel getest konden worden. Hier zijn geen bewaard gebleven scripts voor geschreven.

Het opstarten van verschillende aantallen servers en clients is gedaan door empirisch en handmatig te testen. Opstarten gebeurt met het test.py script en geeft geen problemen, behalve performancedrops bij grote aantallen pythonprocessen. De geteste functies werken zoals verwacht, onafhankelijk van het aantal scripts. Per client worden slechts enkele gegevens bijgehouden (ip, poort, naam, laatst gezien), dus is het maximale aantal clients in principe alleen beperkt door de grootte van het werkgeheugen (plus swap). De FNWI-computers draaien soepel tot ongeveer 40 clients, maar dit hangt natuurlijk ook van de client-GUI af. De server zelf lijkt geen duidelijk minimum aan clients te hebben. Het opstarten van de clients op externe computers geeft eveneens geen problemen, voor zover te testen is met handwerk.

Opstarten van servers van een bevriende groep (ook in Python geschreven) geven geen onverwachte resultaten. Het geheel blijft werken. Dit bleek ook al tijdens de testdag.


%%    Evaluatie van de planning en groepssamenwerking (wat waren de knelpunten, was de gekozen taal goed?)
\section{Planning \& Samenwerking}
Door het leren werken met Python heeft de planning in het begin enige vertraging opgelopen. De implementatie heeft daarna niet meer tijd gekost dan ingepland. De testfase is iets ingekort, waardoor geen tijd beschikbaar was om uitgebreide unit-tests te schrijven. Toch is er uitgebreid getest met de bestaande client, control server, en een aantal servers. Het verslag is, mede door drukte met het voorbereiden van tentamens en het aangeboden uitstel, iets later af dan oorspronkelijk gepland. Voor de rest is de planning aardig nageleefd.

De samenwerking verliep soepel. Het samenwerken ging goed op de momenten dat het nodig was, en zelfs op veel momenten dat individuele deeltaken waren gedefinieerd heeft de hele groep gezamenlijk gewerkt. Alle leden hebben genoeg bijgedragen en gewerkt aan de delen van het project waar ze goed in zijn. De individuele bijdrages werden zo afgewisseld met momenten van gezamenlijk brainstormen en implementeren. Met een tevreden gevoel sluiten we met dit verslag een succesvol en misschien zelfs leuk project af :-)


%%    Feedback over de opgave naar de docent / assistent
\section{Feedback}
De keuze voor UDP is leuk, maar zorgt wel voor extra werk door het wachten op pakketten die (indirect) bevestigen dan pakketten van onze server zijn aangekomen. Naar onze mening geeft het dus nadelen. Het chatsysteem is geen real time applicatie dus het iets langzamer TCP/IP zou geen probleem moeten zijn. Daarbij mogen in het huidige protocol pakketten niet verloren gaan, omdat er meestal geen ontvangstbevestigingen worden gestuurd. Het overwegen van TCP voor een volgend jaar is volgens ons een goed idee.

Zoals eerder vermeld zijn er dus problemen met het checken van de ontvangst van berichten. Hiervoor zouden enkele extra packages kunnen worden gedefinieerd. Ook is bijvoorbeeld het doorsturen van alle namen na het aanmelden van een nieuwe client niet efficient.

Naast deze punten van verbetering zijn we erg te spreken over de opdracht. We hebben enkele weken gewerkt aan dit systeem en hebben het gevoel veel geleerd te hebben. Ook de vrijheid in de programmeertaal en de manier van werken hebben we als prettig ervaren. Tot slot was de testdag niet alleen nuttig, maar ook erg leuk. We zouden dit practicum dus zeker terug willen zien komen voor de komende generatie informaticastudenten!


\section{Conclusie}
%eventueel TODO
In zeven weken tijd is de basis geleerd voor gedistribueerde systemen en een simpel gedistribueerd chatsysteem gebouwd. De in Python geschreven server handelt clients af en communiceert met naburige servers. Een control server kan geraadpleegd worden door clients of servers voor het opvragen van IP-adressen van servers. Test met clients en servers verspreid over verschillende computers tonen een werken systeem waarin servers kunnen uitvallen zonder daarbij een onbruikbaar netwerk achter te laten.

In het proces zijn verder nuttige vaardigheden ontwikkeld met betrekking tot gebruik van versiebeheer, latex en Python, en het samenwerken in een groep. Daarnaast hebben we veel plezier gehad tijdens de vele uren doorprogrammeren in de late avondjes, de gezamenlijke maaltijden, de creatieve momenten en het bier en vuurwerk bij de afsluiting.






\appendix
\begin{changemargin}{-1cm}{-1cm}
\section{Code}

{ \tiny

\subsection*{Server.py}
\begin{verbatim}
import socket, time, os, random, string, sys
from threading import Thread, Lock
from Package import *
from Protocol import *
from xml.dom import minidom 

class Server:

  ## data objects (for clearance all class variables are shown here)
  debug = None
  # OOP-related
  protocol   = None # instance of protocol class
  socket     = None # We need just one for UDP
  tag        = None
 
  mutes_parent    = None
  mutex_children  = None
  mutex_leaves    = None
  mutex_responses = None
  mutex_servers   = None
  mutes_clients   = None

  # network-related
  parent   = None   # ID, ip, port
  children = None   # For each server: ID, ip, port
  leaves   = None   # Connected clients with THIS server
  servers  = None   # (Additional servers) for each server: ID, closest.server
  clients  = None   # connected clients with all servers
  
  responses = None  # expected responses from clients/server/main server
  
  # constructor
  def __init__(self):
    self.parent    = None
    self.admins    = {}
    self.children  = []
    self.leaves    = []
    self.clients   = []
    self.responses = [] # wat/van wie/tot wanneer
    
    self.running = False # wordt gebruikt om de ping_thread netjes te stoppen als de main thread stopt.
    
    self.mutex_parent    = Lock()
    self.mutex_children  = Lock()
    self.mutex_leaves    = Lock()
    self.mutex_responses = Lock()
    self.mutex_servers   = Lock()
    self.mutex_clients   = Lock()
    
    self.mutex_printdb = Lock()

    self.import_settings_from_file()
    self.socket_setup()
    
    self.protocol = Protocol(self)
   
    self.generate_unique_tag()
   
    self.printdb("Connecting to control server")
    self.protocol.hello_to_control_601( self.central_server['ip:port'], self.tag)
    
    self.printdb("Starting ping and responses thread")
    self.running = True
    
    self.ping_thread = Thread(target = self.ping_loop)
    self.ping_thread.start()
    
    self.responses_thread = Thread(target = self.control_responses)
    self.responses_thread.start()
    
    self.printdb("Starting main loop")
    
    try:
      self.loop()
    except (KeyboardInterrupt, SystemExit):
      self.stop()


  def import_settings_from_file(self): 
    doc = minidom.parse("settings.xml")

    logging = doc.getElementsByTagName("logging")[0]

    self.debug = self.get_text_element(logging, "debug") == "true"
    self.log_path = self.get_text_element(logging, "path")
    
    if not self.debug:
      self.logfile = open(self.log_path + str(time.time()) + ".txt", "w")
      sys.stderr = self.logfile
   
    self.printdb("Starting Chat Server")
    self.printdb(time.ctime(time.time()) + '\n')
    self.printdb("Importing settings from settings.xml...")
    
    administrators = doc.getElementsByTagName("administrator")
    for administrator in administrators:
      username = self.get_text_element(administrator, "username")
      password = self.get_text_element(administrator, "password")
      self.admins[username] = password
    self.printdb("Found " + str(len(self.admins)) + " administrators")
    
    connection_settings = doc.getElementsByTagName("connection")[0]
    self.port = int(self.get_text_element(connection_settings, "port"))
    self.host = self.get_text_element(connection_settings, "host")
    self.printdb("Using IP:   " + str(self.host))
    self.printdb("Using Port: " + str(self.port))
    
    cserver_settings = doc.getElementsByTagName("centralserver")[0]
    ip = self.get_text_element(cserver_settings, "address")
    port = int(self.get_text_element(cserver_settings, "port"))
    self.central_server = {'ip': ip, 'volgnummer': 0, 'ip:port':(ip, port)}
    self.printdb("Using controlserver at: " + str(self.central_server['ip:port']))
      
  # Searches a child of root with given name and returns its text value
  def get_text_element(self, root, name):
    return root.getElementsByTagName(name)[0].firstChild.data.strip()
  
  # debug print statement (define debug in settings.xml)
  def printdb(self, message):
    self.mutex_printdb.acquire()
    if self.debug:
      print message
    else:
      self.logfile.write(message + "\n")
      self.logfile.flush()
    self.mutex_printdb.release()
  
  # setup UDP socket
  def socket_setup(self):
    self.printdb("Initializing socket\n")
    self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    

  # control thread: check if expected packages have arrived
  def control_responses(self):
    self.printdb("Control thread started")
    while self.running:
      # loop door lijst met verwachte responses
      # als houdbaarheid voorbij, doe een gepaste actie
      # note: wat een gepaste actie is hangt van de situatie af,
      # maar in bijna alle gevallen het sturen van een package dat
      # in het huidige protocol niet is gedefinieerd.
      self.mutex_responses.acquire()
      for i in range(0, len(self.responses)):
        pass  # not implemented. see report.
      self.mutex_responses.release()
    
      # schedule other thread
      time.sleep(0)
    self.printdb("Responses thread exited")

  # ping thread: ping inactive clients and servers.
  # disconnect when no response on the ping has been received
  def ping_loop(self):
    self.printdb("Ping thread started")
    while self.running:
      now = time.time()
      everything = self.protocol.get_everything()
      for node in everything:
        if (node['last_updated']):

          # no ping received (30 + 2 sec):
          if (now - node['last_updated'] > 32):
            if ('name' in node):
              self.printdb("Client: " + node['name'] + " 32 seconds inactive.")
              self.mutex_leaves.acquire()
              self.leaves.remove(node)
              self.mutex_leaves.release()
              self.printdb("Disconnecting client: " + node['name'])
              for send_node in everything:
                if (send_node != node):
                  self.protocol.removed_client_130(send_node['ip:port'], node['name'], "Client not responding")
            else:
              self.printdb("Server: " + str(node['ip:port']) + " 32 seconds inactive.")
              is_parent = False
              self.mutex_parent.acquire()
              if (node == self.parent):
                is_parent = True
                self.parent = None                
              self.mutex_parent.release()
              if (not is_parent):
                self.mutex_children.acquire()
                self.children.remove(node)            
                self.mutex_children.release()

              self.protocol.peer_server_disconnected_603(self.central_server['ip:port'], node['tag'])

          # inactive for 30 sec: send ping
          elif (now - node['last_updated'] > 30):
            self.printdb("Node: " + str(node['ip:port']) + " 30 seconds inactive.")
            self.protocol.ping_140(node['ip:port'])

      time.sleep(0.5)
    self.printdb("Ping thread exited")

  # loop: listen on socket, waiting for new packages
  def loop(self):
    while  True:
      self.printdb("Waiting for a new package")
      raw_package, addr = self.socket.recvfrom(200)
      package = Package.from_binary(raw_package)
      self.printdb("Received a new package from: " + str(addr))
      self.printdb("Starting new thread to handle the received " + str(package.code) + " package")
      # new package received: start thread to handle
      # and (in this thread) go on listening for even more packages
      thread = Thread(target = self.protocol.handle_input, args = (package, addr))
      thread.start()

  def find_connected_node_by_addr(self, addr):     
    everything = self.protocol.get_everything()
    for node in everything:
      if (node['ip:port']):
        if (node['ip:port'] == addr):
          return node

  def generate_unique_tag(self):
    self.printdb("Generating unique tag")
    pid    = os.getpid()
    length = 28 - len(str(pid))
    temp   = ''
    random.seed(time.time() * self.port * pid)
    for i in range(length):
      temp += random.choice(string.letters + string.digits)
    self.tag = "T04S" + str(pid) + temp
    self.printdb("Generated tag: " + self.tag)

  def stop(self):
    self.printdb("Gracefully terminating all threads before quitting")
    self.running = False
    self.ping_thread.join()  
    self.responses_thread.join()
    
    for leaf in self.leaves:
      self.protocol.quit_701(leaf['ip:port'], "Server is disconnecting")
    
    self.printdb("Bye bye!")
    
    if not self.debug:
      self.logfile.close()
\end{verbatim}


\subsection*{Protocol.py}
\begin{verbatim}

import socket
import re
import time
import random
import string
from Package import *

class Protocol:
  
  def __init__(self, server):
    self.server     = server
    self.volgnummer = 0    

  # Aanroeper van deze functie moet leaves niet vast hebben, anders: deadlock
  def unique_name(self, name):      
    self.server.mutex_leaves.acquire()
    for leaf in self.server.leaves:
      if name.lower() == leaf['name'].lower():
        self.server.mutex_leaves.release()
        return False
    self.server.mutex_leaves.release()
    
    self.server.mutex_clients.acquire()
    for child in self.server.clients:
      if name.lower() == child['name'].lower():
        self.server.mutex_clients.release()
        return False
    self.server.mutex_clients.release()
    
    return True
  
  # handle input: take package and call method suitable for package
  def handle_input(self, package, addr):

    # Update the "last updated" timestamp for the sending node
    node = self.server.find_connected_node_by_addr(addr)
    if (node):
      node['last_updated'] = time.time()
    
    self.server.printdb("Selecting handler for a package with code: " \
                                                            + str(package.code)) 
    # call the right handle method
    if   package.code == 100: self.handle_100(package, addr)
    elif package.code == 110: self.handle_110(package, addr)
    elif package.code == 120: self.handle_120(package, addr)
    elif package.code == 130: self.handle_130(package, addr)
    elif package.code == 140: self.handle_140(package, addr)
    elif package.code == 150: self.handle_150(package, addr)
    elif package.code == 160: self.handle_160(package, addr)
    elif package.code == 170: self.handle_170(package, addr)
    elif package.code == 200: self.handle_200(package, addr)
    elif package.code == 210: self.handle_210(package, addr)
    elif package.code == 300: self.handle_300(package, addr)
    elif package.code == 310: self.handle_310(package, addr)
    elif package.code == 600: self.handle_600(package, addr)
    elif package.code == 602: self.handle_602(package, addr)
    elif package.code == 604: self.handle_604(package, addr)
    elif package.code == 700: self.handle_700(package, addr)
    elif package.code == 701: self.handle_701(package, addr)
    else: self.server.printdb("Unable to handle a " + str(package.code) \
                                       + " package, package ingnored.")

  # send package using the socket
  # servers will respond to the port of the socket,
  # so this has to be the same socket as for incoming packages
  def send_package(self, package, addr):
    self.server.socket.sendto(package.to_binary(), addr)
    self.volgnummer = self.volgnummer + 1

  # get list of all leaves, children and the parent
  def get_everything(self, addr = False):
    everything = list()
    
    self.server.mutex_parent.acquire()
    if self.server.parent != None:
      if addr:
        if self.server.parent['ip:port'] != addr:
          everything.append(self.server.parent)
      else:
        everything.append(self.server.parent)      
    self.server.mutex_parent.release()

    self.server.mutex_children.acquire()
    if self.server.children:
      everything.extend(self.server.children)
    self.server.mutex_children.release()
      
    self.server.mutex_leaves.acquire()
    if self.server.leaves:
      everything.extend(self.server.leaves)
    self.server.mutex_leaves.release()
    
    return everything
      
  def get_all_leaves(self):
    all_leaves = list()
    
    self.server.mutex_leaves.acquire()
    if self.server.leaves:
      all_leaves.extend(self.server.leaves)
    self.server.mutex_leaves.release()      

    self.server.mutex_clients.acquire()
    if self.server.clients:
      all_leaves.extend(self.server.clients)
    self.server.mutex_clients.release()
    
    return all_leaves

  def get_client_by_addr(self, addr):
    self.server.mutex_leaves.acquire()
    for leaf in self.server.leaves:
      if leaf['ip:port'] == addr:
        self.server.mutex_leaves.release()
        return leaf['name']             
    self.server.mutex_leaves.release()
    
    return False

  # send private message
  def whisper(self, sender, message, receiver, func, addr = False):
    done = False
    
    self.server.mutex_leaves.acquire()
    for leaf in self.server.leaves:
      if leaf['name'] == receiver:
        func(leaf['ip:port'], sender, message, receiver)
        done = True
        break
    self.server.mutex_leaves.release()

    if not done:
      self.server.mutex_parent.acquire()
      if self.server.parent != None:
        if addr:
          if self.server.parent['ip:port'] != addr:
            func(self.server.parent['ip:port'], sender, message, receiver)
        else:
          func(self.server.parent['ip:port'], sender, message, receiver)
      self.server.mutex_parent.release()

      self.server.mutex_children.acquire()
      for child in self.server.children:
        func(child['ip:port'], sender, message, receiver)
      self.server.mutex_children.release()


  ## Handle methods

  def handle_100(self, package, addr):
    name     = package.get_word(0)
    password = package.get_word(1)

    if not self.unique_name(name):
      self.server.printdb("Registration from " + str(addr) + " failed. " + name+ " is not a unique name")
      self.registration_failed_510(addr, "Registration failed: name already in use\n")
    elif name in self.server.admins and self.server.admins[name] != password:
      self.server.printdb("Registration from " + str(addr) + " failed. wrong password provided\n")
      self.registration_failed_510(addr, "Registration failed: wrong password provided")
    elif not re.match("^[A-Za-z0-9_]*$", name):
      self.server.printdb("Registration from " + str(addr) + " failed. " + name + " contains illegal name characters\n")
      self.registration_failed_510(addr, "Name may only consist of letters, numbers and underscores")
    else:
      self.server.mutex_leaves.acquire()
      self.server.leaves.append({'ip:port': addr, 'name': name, 'last_updated': time.time()})
      self.server.mutex_leaves.release()
      
      self.server.printdb("Client added as leaf with name: " + name + '\n')
      self.registration_accepted_500(addr)
      
      everything = self.get_everything()
      all_leaves = self.get_all_leaves()
     
      if everything:
        # Make sure the whole world knows about the new client, including himself
        for node in everything:
          self.client_added_110(node['ip:port'], 1, 1, name)
      
      if all_leaves:
        msg_counter = 1
          
        for node in all_leaves:
          # Now give the client a list of the whole world, but excluding himself since we got that fixed in step 1
          if node['ip:port'] != addr:
            self.client_added_110(addr, msg_counter, len(all_leaves) - 1, node['name'])
            msg_counter = msg_counter + 1
  
  def handle_110(self, package, addr):      
    content = package.get_word(0)
    current = content[0:6]
    length  = content[6:12]
    
    if int(length) == 0:
      return
      
    name  = content[12:]
    found = False
        
    # Get tag of server who send this
    server_tag = False
 
    self.server.mutex_parent.acquire()
    if self.server.parent != None:
      if self.server.parent['ip:port'] == addr:
        server_tag = self.server.parent['tag']
    self.server.mutex_parent.release()
    
    if not server_tag:
      self.server.mutex_children.acquire()
      for node in self.server.children:
        if node['ip:port'] == addr:
          server_tag = node['tag']
      self.server.mutex_children.release()

    # not found:
    if True:
      self.server.mutex_clients.acquire()
      self.server.clients.append({'ip:port':addr, 'name':name, 'last_updated': time.time(), 'server_tag':server_tag})
      self.server.mutex_clients.release()

      self.server.mutex_parent.acquire()
      if self.server.parent != None:
        if self.server.parent['ip:port'] != addr:
          self.client_added_110(self.server.parent['ip:port'], 1, 1, name)
      self.server.mutex_parent.release()

      self.server.mutex_children.acquire()
      for node in self.server.children:
        if node['ip:port'] != addr:
          self.client_added_110(node['ip:port'], 1, 1, name)
      self.server.mutex_children.release()
      
      # Give the clients an update too
      self.server.mutex_leaves.acquire()
      for node in self.server.leaves:
        self.client_added_110(node['ip:port'], 1, 1, name)
      self.server.mutex_leaves.release()
    else:
      self.server.printdb("Something went wrong with: " + name)
      
  def handle_120(self, package, addr):
    client  = self.server.find_connected_node_by_addr(addr)
    message = package.get_text(0)
       
    self.server.mutex_leaves.acquire()
    if client in self.server.leaves: 
      self.server.leaves.remove(client) # remove client from list
      self.server.mutex_leaves.release()
      self.server.printdb("Client: " + client['name'] + " left\n")

      everything = self.get_everything()
    
      if everything:
        for node in everything:
          self.removed_client_130(node['ip:port'], client['name'], message)
    else:
      self.server.mutex_leaves.release()
      self.server.printdb("A client with addr "+str(addr)+" wanted to disconnect, but wasn't connected to this server\n")

  def handle_130(self, package, addr):
    name    = package.get_word(0)
    message = package.get_text(1)
    
    # search for leaving client
    for leaf in self.server.clients:
      if leaf['name'] == name:
        self.server.mutex_clients.acquire()
        self.server.clients.remove(leaf)  # remove client from list
        self.server.mutex_clients.release()
        break

    # When a connection is ended, but not by the client itself:
    if len(message) < 1:
      message = "Leaving"

    everything = self.get_everything(addr)

    if everything:
      for node in everything:
        if node['ip:port'] != addr:
          self.removed_client_130(node['ip:port'], name, message)

  def handle_140(self, package, addr):
    self.pong_150(addr, package.get_word(0))

  def handle_150(self, package, addr):
    pass
    
  def handle_160(self, package, addr):
    new_name = package.get_word(0)
    # password while name changing not specified in given protocol
   
    if not re.match("^[A-Za-z0-9_]*$", new_name):
      self.name_change_failed_530(addr, "Name may only consist of letters, numbers or underscores")
      return
    
    # find old_name of client
    self.server.mutex_leaves.acquire()
    for leaf in self.server.leaves:
      if leaf['ip:port'] == addr:
        old_name = leaf['name']
        
        self.server.mutex_leaves.release()            # release for unique_name function
        not_unique = not (self.unique_name(new_name)) # wierd construction needed. Else u would call
        self.server.mutex_leaves.acquire()            # release() when you haven't locked it yet.

        if old_name == new_name:
          self.name_change_failed_530(addr, "Requested name is the same as your current name")
          break
        elif not_unique:
          self.name_change_failed_530(addr, "The requested name allready exists")
          break
        else:          
          leaf['name'] = new_name # save name change

          self.name_change_successfull_520(addr)
          
          # send name change to all connected servers and connected clients
          self.server.mutex_parent.acquire()
          if self.server.parent != None:
            self.name_changed_170(self.server.parent['ip:port'], old_name, new_name)
          self.server.mutex_parent.release() 
          
          self.server.mutex_children.acquire()
          for node in self.server.children:
            self.name_changed_170(node['ip:port'], old_name, new_name)
          self.server.mutex_children.release()

          # Don't forget the person himself so he gets an updated list too
          for leaf in self.server.leaves:
            self.name_changed_170(leaf['ip:port'], old_name, new_name)
              
    self.server.mutex_leaves.release()

  def handle_170(self, package, addr):
    old_name = package.get_word(0)
    new_name = package.get_word(1)
    
    self.server.mutex_clients.acquire()
    for leaf in self.server.clients:
      if leaf['name'] == old_name:
        leaf['name'] = new_name
    self.server.mutex_clients.release()
    
    everything = self.get_everything(addr)

    self.server.mutex_leaves.acquire()    
    if everything:
      for node in everything:
        if node['ip:port'] != addr: 
          self.name_changed_170(node['ip:port'], old_name, new_name)
    self.server.mutex_leaves.release()

  def handle_200(self, package, addr):
    self.handle_200_210(package, addr, self.pass_text_message_300)

  def handle_210(self, package, addr):
    self.handle_200_210(package, addr, self.pass_action_310)
   
  def handle_200_210(self, package, addr, func):
    sender = None

    self.server.mutex_leaves.acquire() 
    for leaf in self.server.leaves:
      if leaf['ip:port'] == addr:
        sender = leaf['name']
    self.server.mutex_leaves.release()

    if sender == None:
      self.server.printdb("Unknown client wants to send a message.")
      return
    
    receiver = package.get_word(0)
    message  = package.get_text(1)

    if receiver == "#all":
      everything = self.get_everything()

      self.server.mutex_leaves.acquire()
      if everything:      
        for node in everything:
          func(node['ip:port'], sender, message, '#all')
      self.server.mutex_leaves.release()
    else:
      func(addr, sender, message, sender)
      self.whisper(sender, message, receiver, func, addr)

  def handle_300(self, package, addr):    
    self.handle_300_310(package, addr, self.pass_text_message_300)

  def handle_310(self, package, addr):
    self.handle_300_310(package, addr, self.pass_action_310)


  def handle_300_310(self, package, addr, func):

    sender   = package.get_word(0)
    receiver = package.get_word(1)
    message  = package.get_text(2)
    
    if receiver == "#all":
      everything = self.get_everything()

      self.server.mutex_leaves.acquire()
      if everything:      

        for node in everything:
          if node['ip:port'] != addr:
            func(node['ip:port'], sender, message, '#all')
      self.server.mutex_leaves.release()
    else:
      self.whisper(sender, message, receiver, func, addr)
  
  def handle_600(self, package, addr):    
    # voeg server toe aan lijst
    print "Nieuwe server als child: " + package.get_word(0)
    
    self.server.mutex_children.acquire()
    self.server.children.append({'ip:port': addr, 'tag': package.get_word(0), 'last_updated': time.time()})
    self.server.mutex_children.release()
    
    all_leaves = self.get_all_leaves()
    
    msg_counter = 1

    # send own client list to new child
    for leaf in all_leaves:
      self.client_added_110(addr, msg_counter, len(all_leaves) - 1, leaf['name'])
      msg_counter = msg_counter + 1

    if all_leaves:
      msg_counter = 1
          
      for node in all_leaves:
         # Now give the client a list of the whole world, but excluding himself since we got that fixed in step 1
        if node['ip:port'] != addr:
          self.client_added_110(addr, msg_counter, len(all_leaves) - 1, node['name'])
          msg_counter = msg_counter + 1
    else:
      self.client_added_110(addr, 0, 0)

  def handle_602(self, package, addr):    
    # 604 kan 602 doorsturen :)
    if isinstance(package, basestring):
      temp = package
    else:
      temp = package.get_word(0)

    if temp == 'none':
      self.server.printdb("Server initialized as root\n")
    else:
      pack = temp.split(':')
      print "dit is pack: " + str(pack)
      self.server.mutex_parent.acquire()
      self.server.parent = {'ip:port': (pack[0], int(pack[1])), 'tag': pack[2], 'volgnummer': 0, 'last_updated': time.time()}
      self.server.mutex_parent.release()
      self.register_server_600((pack[0], int(pack[1])), self.server.tag)
      self.server.printdb("Server: " + self.server.parent['tag'] + " set as parent\n")
      
      # send all clients that I've got
      all_leaves = self.get_all_leaves()
      
      if all_leaves:
        print "Ik print nu alle namen die ik zou moeten versturen! " + str(all_leaves) 
        msg_counter = 1
          
        for node in all_leaves:
          print "En nu stuur ik: " + str(node['name'])
          self.client_added_110((pack[0], int(pack[1])), msg_counter, len(all_leaves) - 1, node['name'])
          msg_counter = msg_counter + 1
      else:
        self.client_added_110((pack[0], int(pack[1])), 0, 0)

  def handle_604(self, package, addr):
    fallen_server = package.get_word(0)
    tag           = package.get_word(1) # Could be empty
    
    # Update client list
    self.server.mutex_clients.acquire()
    self.server.mutex_leaves.acquire()
    self.server.mutex_children.acquire()
    self.server.mutex_parent.acquire()
        
    to_remove = list()
    
    if self.server.clients:
      for client in self.server.clients:  
        if client['server_tag'] == fallen_server:  
          # Make sure chat clients can update their list too
          if self.server.leaves:
            for leaf in self.server.leaves:
              self.removed_client_130(leaf['ip:port'], client['name'], 'Server disconnect')
          
          # Make sure children servers update their list too
          if self.server.children:
            for node in self.server.children:
              if node['tag'] != fallen_server:
                self.removed_client_130(node['ip:port'], client['name'], 'Server disconnect')
          
          if self.server.parent != None and self.server.parent['tag'] != fallen_server:
            self.removed_client_130(self.server.parent['ip:port'], client['name'], 'Server disconnect')
            
          to_remove.append(client)
          
      for element in to_remove:
        self.server.clients.remove(element)
      
    self.server.mutex_parent.release()
    self.server.mutex_children.release()
    self.server.mutex_leaves.release()
    self.server.mutex_clients.release()
    
    if tag:
      self.server.parent = None
      self.server.printdb("GETTING A NEW PARENT tag: " + str(tag))
      self.handle_602(tag, addr)
    else:
      to_remove = list()
      
      if self.server.children:
        for node in self.server.children:
          if node['tag'] == fallen_server:
            to_remove.append(node)
            
      for element in to_remove:
        self.server.children.remove(element)
  
  def handle_700(self, package, addr):
    # Check if invoked by admin
    self.server.mutex_leaves.acquire()
    for leaf in self.server.leaves:
      if leaf['ip:port'] == addr:
        if leaf['name'] in self.server.admins:
          # TODO
          #self.quit_701()
          self.server.stop()
        break
    self.server.mutex_leaves.release()
    
  def handle_701(self, package, addr): 
    # Get disconnecting server
    server = self.server.find_connected_node_by_addr(addr) 
    msg    = "Server: " + server['tag'] + " disconnected." 
    self.server.printdb(msg)
    self.peer_server_disconnect_603(self.central_server['ip:port'], server['tag'])
    
  # Aan de toegevoegde client of server wordt  in een serie berichten van dit 
  # type alle namen van verbonden clienten doorgegeven. Omdat deze boodschappen 
  # ook worden gebruikt om een reeks namen door te geven, wordt een volgnummer 
  # en een totaal aantal te versturen elementen meegegeven. Volgnummers lopen 
  # van 1 - lijstlengte. Gebruik voor de getallen een veldbreedte van 6 tekens. 
  # Bij de aanmelding van een server moeten ook dergelijke boodschappen worden 
  # uitgewisseld (zie verderop). Omdat er niet altijd clienten zullen zijn, 
  # mogen dan volgnummer en lijstlengte beide nul te zijn.
  def client_added_110(self, target, serial_number, list_length, name = None):
    msg = "Sending a 110 to: " + str(target)        + \
                " with name: " + str(name)          + \
                  ", serial: " + str(serial_number) + \
          " and list length: " + str(list_length)
    self.server.printdb(msg)
    
    package = Package(110, self.volgnummer)
    if name:
      package.add_word(str(serial_number).zfill(6) + str(list_length).zfill(6) + name)
    else:
      package.add_word(str(serial_number).zfill(6) + str(list_length).zfill(6))
    self.send_package(package, target)
    
  # Dit bericht wordt verzonden als de client-server verbinding verbroken is. 
  # De rest van het bericht bevat de naam van de verwijderde client en een tekst
  # die naar iedereen als groet verzonden wordt. Mocht de client deze niet zelf
  # invullen of wordt de verbinding onverwachts verbroken dan dient de server 
  # dit zelf in te vullen.
  def removed_client_130(self, target, name, message = None):
    msg = "Sending a 130 to: " + str(target)  + \
                " with name: " + str(name)    + \
              " and message: " + str(message)
    self.server.printdb(msg)
    
    package = Package(130, self.volgnummer)
    package.add_word(name)
    package.add_word(message)
    self.send_package(package, target)
    
  # De rest van het bericht dient 1 woord  te bevatten: een identificatie 
  # nummer tekenreeks. Het antwoord op dit bericht dient binnen 2 seconden
  # verstuurd te zijn, anders mag de verbinding verbroken worden. Omdat met UDP 
  # wordt gewerkt, is het niet altijd zeker of berichten aankomen. Daarom mag 
  # indien geen  pong wordt ontvangen, het ping-verzoek een paar keer worden 
  # herhaald met hetzelfde identificatienummer.
  def ping_140(self, target):
    id = self.generate_ping_tag()
    msg = "Sending a 140 to: " + str(target) + \
                  " with id: " + str(id)
    self.server.printdb(msg) 
    package = Package(140, self.volgnummer)
    package.add_word(id)
    self.send_package(package, target)

  # De rest van het bericht dient 1 woord te bevatten: het identificatienummer / 
  # tekenreeks van het voorafgaande Echo Request. Bericht dient binnen 2 sec na
  # het ontvangen van een ping verstuurd te zijn, anders mag de verbinding 
  # verbroken worden door de andere partij.
  def pong_150(self, target, echo_request):
    msg =   "Sending a 150 to: " + str(target)       + \
          " with echo request: " + str(echo_request)
    self.server.printdb(msg)
    package = Package(150, self.volgnummer)
    package.add_word(echo_request)
    self.send_package(package, target)

  # Dit bericht wordt aan alle aangesloten clienten verzonden als een client 
  # zijn naam heeft gewijzigd. Het eerste woord in het datadeel is de oude naam, 
  # het tweede de nieuwe naam.
  def name_changed_170(self, target, old_name, new_name):
    msg = "Sending a 170 to: " + str(target)   + \
            " with old name: " + str(old_name) + \
             " and new name: " + str(new_name)
    self.server.printdb(msg)
    package = Package(170, self.volgnummer)
    package.add_word(old_name)
    package.add_word(new_name)
    self.send_package(package, target)

  # De rest van het bericht bestaat uit 3 delen: het eerste woord is de naam van
  # de afzender, het tweede woord is de naam van degene aan wie het bericht 
  # gericht is of de tekst #all als het bericht naar iedereen gaat. De rest is 
  # de door de persoon ingevoerde tekst.
  def pass_text_message_300(self, target, sender, message, receiver = None):
    msg = "Sending a 300 to: " + str(target)   + \
              " with sender: " + str(sender)   + \
                 ", message: " + str(message)  + \
             " and receiver: " + str(receiver) 
    self.server.printdb(msg)
    package = Package(300)
    package.add_word(sender)
    if (receiver == None):
      package.add_word("#all")
    else:
      package.add_word(receiver)
    package.add_word(message)
    self.send_package(package, target)
     
  def pass_action_310(self, target, sender, message, receiver = None):
    msg = "Sending a 310 to: " + str(target)   + \
              " with sender: " + str(sender)   + \
                 ", message: " + str(message)  + \
             " and receiver: " + str(receiver) 
    self.server.printdb(msg)
    package = Package(310)
    package.add_word(sender)
    if (receiver == None):
      package.add_word("#all")
    else:
      package.add_word(receiver)
    package.add_word(message)
    self.send_package(package, target)
  
  # Wordt verzonden als de registratie gelukt is, de rest van het bericht kan 
  # leeg zijn.
  def registration_accepted_500(self, target):
    msg = "Sending a 500 to: " + str(target)
    self.server.printdb(msg)
    package = Package(500, self.volgnummer)
    self.send_package(package, target)

  # Wordt verzonden als de registratie mislukt is. De rest van het bericht bevat
  # de reden van het mislukken van de inlogpoging.
  def registration_failed_510(self, target, message):
    msg = "Sending a 510 to: " + str(target)  + \
             " with message: " + str(message)
    self.server.printdb(msg)
    package = Package(510)
    package.serial = 1 # TODO Echte serial! 
    package.add_word(message)
    self.send_package(package, target)
    
  # Wordt verzonden als de naamswijziging gelukt is, de rest van het bericht kan
  # leeg zijn.
  def name_change_successfull_520(self, target):
    msg = "Sending a 520 to: " + str(target)
    self.server.printdb(msg)
    package = Package(520)
    self.send_package(package, target)
    
  # Wordt verzonden als de naamswijziging mislukt is. De rest van het bericht 
  # bevat de reden van het mislukken van de pogin.
  def name_change_failed_530(self, target, message):
    msg = "Sending a 530 to: " + str(target)  + \
             " with message: " + str(message)
    self.server.printdb(msg)
    package = Package(530)
    package.add_word(message)
    self.send_package(package, target)
  
  # Dit verstuurt een server om zich aan te melden bij een andere server. Het is
  # dus de server-server versie van berichtype 100. De ontvangende server moet 
  # ip adres en poortnummer van de afzender uit de ip header halen.
  def register_server_600(self, target, tag):
    msg = "Sending a 600 to: " + str(target) + \
                 " with tag: " + str(tag)
    self.server.printdb(msg)
    package = Package(600, self.volgnummer)
    package.add_word(tag)
    self.send_package(package, target)

  # Dit verstuurt een server bij aanmelding naar de control server. De 
  # ontvangende server moet ip adres en poortnummer van de afzender uit de ip 
  # header halen.
  def hello_to_control_601(self, target, tag):
    msg = "Sending a 601 to: " + str(target) + " with tag: " + str(tag)
    self.server.printdb(msg)
    package = Package(601, self.volgnummer)
    package.add_word(tag)
    self.send_package(package, target)
  
  # Dit verstuurt een server naar de control server als een van zijn partner 
  # servers niet reageert. Dat kan dus de parent of een van de kinderen zijn. De
  # huidige implementatie van de control server neemt een 603 altijd voor waar 
  # aan en probeert niet of de betreffende server misschien toch reageert.    
  def peer_server_disconnected_603(self, target, tag):
    msg = "Sending a 603 to: " + str(target) + \
                 " with tag: " + str(tag)
    self.server.printdb(msg)
    package = Package(603)
    package.add_word(tag)
    self.send_package(package, target)

  # Een server kan dit bericht versturen naar  zijn buren als hij gaat stoppen, 
  # b.v. na de ontvangst van een 700 bericht.
  def quit_701(self, target, message):
    msg = "Sending a 701 to: " + str(target)  + \
             " with message: " + str(message)
    self.server.printdb(msg)
    package = Package(701)
    package.add_word(message)
    self.send_package(package, target)
    
  def generate_ping_tag(self):
    temp = ""
    for i in xrange(15):
      temp += random.choice(string.letters + string.digits)
    return temp

\end{verbatim}


\subsection*{Package.py}
\begin{verbatim}
from struct import pack, unpack
from Callable import *

class Package:

  def __init__(self, code, serial = 0, content = None):
    self.code    = code
    self.serial  = serial
    self.content = list()
    
    if content != None:
      self.content = content.split(' ') 

  # Creates a new Package instance from a given binary blob
  def from_binary(blob):
    decoded_blob = unpack('!hhH', blob[:6])
    content_length = decoded_blob[0] - 6
    
    if content_length > 194:
      content_length = 194

    encoding = '!hhH' + str(content_length) + 's'
    decoded_blob = unpack(encoding, blob)
      
    return Package(decoded_blob[1], decoded_blob[2], decoded_blob[3])
    
  # Creates a static method from from_binary()
  from_binary = Callable(from_binary)
    
  # Creates a binary blob of this package with the layout in network order:
  # POSITION  TYPE    SIZE  DESCRIPTION
  # 0 - 1     short   2     Message length
  # 2 - 3     short   2     Message code
  # 4 - 5     ushort  2     Serial number
  # 6 - 199   char    194   Message 
  def to_binary(self):
    content_length = len(' '.join(self.content))
    encoding = '!hhH' + str(content_length) + 's'          
    return pack(encoding, content_length + 6, self.code, self.serial, ' '.join(self.content))
    
  def add_word(self, word):
    self.content.append(word)

  def get_word(self, index):
    if index < len(self.content):
      return self.content[index]
    else:
      return False
  
  def get_text(self, index):
    if index < len(self.content):
      return ' '.join(self.content[index:])
    else:
      return False
\end{verbatim}

     
\subsection*{main.py}
\begin{verbatim}
from Server import *
from Protocol import *
from Package import *

if __name__ == "__main__":
  # start server. it will do everything you need
  server = Server()

\end{verbatim}

} % end tiny

\end{changemargin}


\end{document}
